package com.bjtang.fileSync.util;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;

import com.bjtang.fileSync.nio.SocketDto;

/**
 * 文件编码解码器 含输入输出流和byte的基本处理
 */
public class FileCODEC {
	public static byte[] byteMergerAll(byte[]... values) {
		int length_byte = 0;
		for (int i = 0; i < values.length; i++) {
			length_byte += values[i].length;
		}
		byte[] all_byte = new byte[length_byte];
		int countLength = 0;
		for (int i = 0; i < values.length; i++) {
			byte[] b = values[i];
			System.arraycopy(b, 0, all_byte, countLength, b.length);
			countLength += b.length;
		}
		return all_byte;
	}

	/*
	 * 注意!!!! begin和end算法和String 的substring是同样道理 begin从前往后,0开始计算
	 * end从后往前,length开始计算,这样确保了 end-begin始终大于0 begin 最小值为0 最大值为length-1
	 * end最小值为1,最大值为length
	 */
	public static byte[] subByte(byte[] bytes, int begin, int end) {
		int length = end - begin;
		byte[] subbyte = new byte[length];
		for (int i = 0; i < length && i < bytes.length; i++) {
			subbyte[i] = bytes[i + begin];
		}
		return subbyte;
	}

	public static byte[] subByte(byte[] bytes, int begin) {
		int length = bytes.length - begin;
		byte[] subbyte = new byte[length];
		for (int i = 0; i < length; i++) {
			subbyte[i] = bytes[i + begin];
		}
		return subbyte;
	}

	public static void writeChannel(SocketChannel out, byte[] data) throws IOException {
		ByteBuffer buffer = ByteBuffer.wrap(byteMergerAll(data, "\r\n".getBytes()));
		out.write(buffer);
	}

	public static void writeChannel(SocketChannel out, String data) throws IOException {
		ByteBuffer buffer = ByteBuffer.wrap((data + "\r\n").getBytes());
		out.write(buffer);
	}

	public static void writeChannel(SocketChannel out, String[] datas) throws IOException {
		for (int i = 0; i < datas.length; i++)
			writeChannel(out, datas[i]);
	}

	/**
	 * 适用于读取少量数据，或者一行数据 2023.3.14 @bjtang
	 */
	public static byte[] readChannel(SocketChannel socketChannel) throws IOException {
		int count = 0;
		byte[] by = new byte[0];
		byte by2 = 0;
		byte[] by3 = new byte[1];
		ByteBuffer buffer = ByteBuffer.allocate(1);
		while (socketChannel.read(buffer) != -1) {

			buffer.flip();
			by3[0] = buffer.get(0);
			buffer.clear();

			by = byteMergerAll(by, by3);
			if (by2 == 13)
				if (by3[0] == 10)
					break;
			by2 = by3[0];

			count++;
			if (count >= 5000) {
				new ArithmeticException("一行数据过长");
			}
		}
		// 修剪换行回车
		if (by.length == 0)
			return by;

		byte[] retu = new byte[by.length - 2];
		for (int i = 0; i < by.length - 2; i++)
			retu[i] = by[i];
		return retu;
	}

	// 写入数据到指定文件
	public static void channelWriteChanne(SocketChannel socketChannel, File file, long size) throws IOException {
		FileOutputStream fileOutputStream = new FileOutputStream(file);
		FileChannel fileChannel = fileOutputStream.getChannel();
		ByteBuffer buffer = ByteBuffer.allocate(65536);
		long size2 = 0;
		int n = -1;
		//数据需要先>0 否则会导致通道假死
		while (size>0 && -1 != (n = socketChannel.read(buffer))) {
			buffer.flip();
			fileChannel.write(buffer);
			buffer.clear();
			size2 = size2 + n;
			// 防止恶意的无限制的数据写入磁盘
			if (size2 >= size)
				break;
		}
		fileChannel.close();
		fileOutputStream.close();
	}

	// 读入文件数据到指定SocketChannel
	public static void channelReadChanne(SocketChannel socketChannel, File file) throws IOException {
		FileInputStream fileInputStream = new FileInputStream(file);
		FileChannel fileChannel = fileInputStream.getChannel();
		long size = fileChannel.size();
		long postion = 0;
		while (size > 0) {
			long count = fileChannel.transferTo(postion, size, socketChannel);
			postion += count;
			size -= count;
		}
		fileChannel.close();
		fileInputStream.close();
	}

	public static byte[] encoder(SocketDto socketDto) throws UnsupportedEncodingException {
		// 通过文件路径+文件名+32位md5值+ 文件长度的方式进行文件安全校验
		String md5ByString = FileMd5
				.getMd5ByString(socketDto.getFileName() + socketDto.getFileMd5() + socketDto.getFileSize());
		// 数字转字符串填充0 长度为2 则 输出002 , 20则 输出020 121 则 输出121
		// 长度超过位数则原样输出 例 02d 长度333 则输出333 长度 2 则输出 02
		// 因为文件命名不会超过999,所以这里不做判断
		byte[] fileNameLength = String.format("%03d", socketDto.getFileName().getBytes().length).getBytes("UTF-8");
		byte[] fileName = socketDto.getFileName().getBytes("UTF-8");
		byte[] md5Bytes = socketDto.getFileMd5().getBytes("UTF-8");
		byte[] checkBytes = md5ByString.getBytes("UTF-8");
		// 最后传输数据的时候不能变化
		byte[] fileLength = String.valueOf(socketDto.getFileSize()).getBytes("UTF-8");
		byte[] fileLastTime = String.valueOf(socketDto.getFileLastTime()).getBytes("UTF-8");
		byte[] fileMethod = socketDto.getFileMethod().getBytes("UTF-8");
		// 向服务器发送[文件名字节]
		// 向服务器发送[文件名]
		// 向服务器发送[文件md5值]
		// 向服务器发送[文件md5校验]
		// 向服务器发送[文件字节长度]+","
		// 向服务器发送[文件最后修改时间]+","
		// 向服务器发送[操作方法],以\r\n结束
		return byteMergerAll(fileNameLength, fileName, md5Bytes, checkBytes, fileLength, ",".getBytes(), fileLastTime,
				",".getBytes(), fileMethod);
	}

//	解码器
	public static SocketDto decoder(byte[] readData) {
		int parseInt = Integer.parseInt(new String(subByte(readData, 0, 3)));
		String fileName = new String(subByte(readData, 3, 3 + parseInt));
		String md5 = new String(subByte(readData, 3 + parseInt, 35 + parseInt));
		String checkMd5 = new String(subByte(readData, 35 + parseInt, 67 + parseInt));
		// long size = Long.parseLong(new String(ByteUtil.subByte(readData, 67 +
		// parseInt, readData.length)).trim());
		String string = new String(subByte(readData, 67 + parseInt, readData.length)).trim();
		// 包含split
		String[] split = string.split(",");

		String md5ByString = FileMd5.getMd5ByString(fileName + md5 + Long.parseLong(split[0]));
		// 通过文件路径+文件名+md5值+ 文件长度的方式进行文件安全校验
		// 校验成功了应该交给handler进行处理
		if (checkMd5.equals(md5ByString)) {
			SocketDto socketDto = new SocketDto();
			socketDto.setFileMd5(md5);
			socketDto.setFileName(fileName);
			socketDto.setFileSize(Long.parseLong(split[0]));
			socketDto.setFileLastTime(Long.parseLong(split[1]));
			socketDto.setFileMethod(split[2]);
			return socketDto;
		} else {
			return null;
		}
	}
}
